import { getCloudWs, getDeviceUuid } from "..";
import { CLOUD_DEBUG } from "../tokens/args";
import { LogCollection } from "./LogCollection";
import { LogLevel } from "./LogLevel";
import { LogStackFrame } from "./LogStackFrame";
import { LoggerScheme } from "./LoggerScheme";
import { LoggerSchemes } from "./LoggerSchemes";
import { TraceCollection } from "./TraceCollection";
import { TraceFormatter } from "./defaultFormatter";
import { getCallerName, getRawStackTrace, parseTrace, sliceStackFrames } from "./logger";

export class Record {

    /**
     * @description: 用来限制记录的日志级别，低于此级别的日志不会记录。
     */
    private RECORD_LEVEL: number = LogLevel.Debug;

    /**
     * @description: 用来限制显示的日志级别，低于此级别的日志不会被显示出来。
     */
    private DISPLAY_LEVEL: number = LogLevel.Debug;

    constructor(public logStack: LogCollection, private name?: string) { }

    /**
     * @description: 设置记录的日志级别，低于设置的级别的日志都不会记录。
     * 
     * **注意！：**
     * 
     * - 修改前的日志记录不会改变。
     * 
     * @param {number} level 设置的等级。建议使用 `LogLevel` 枚举类型来获取等级。
     */
    public setRecordLevel(level: number): void {
        this.RECORD_LEVEL = level;
    }

    /**
     * @description: 设置显示的日志级别，低于设置的级别的日志都不会显示。
     * @param {number} level 设置的等级。建议使用 `LogLevel` 枚举类型来获取等级。
     */
    public setDisplayLevel(level: number): void {
        this.DISPLAY_LEVEL = level;
    }

    /**
     * @description: 将信息打印到控制台，并带上换行符。 可以一次性传入多个参数，第一个参数作为主要信息，其他参数作为类似于 [printf(3)](https://man7.org/linux/man-pages/man3/printf.3.html) 中的代替值（参数都会传给 `util.format()` ）。
     * 
     * 此函数与 `console.log` 方法的主要区别在于会自动存储每一次的日志，以供后面使用。
     * @param {string} [message] 主要信息。
     * @param {any[]} [args] 要填充的数据。
     * @example
     * ```typescript
     * const count: number = 5;
     * 
     * // 打印 'count: 5' 到 stdout
     * Record.log('count: %d', count);
     * 
     * // 打印 'count: 5' 到 stdout
     * Record.log('count:', count);
     * ```
     */
    public log(message?: string, ...args: any[]): string {
        return this.recLog(LoggerSchemes.log, true, message, ...args);
    }

    /**
     * @description: 与 `Record.log` 类似，但输出结果以灰色字体显示。输出优先级低于 `log` ，用于输出观察性质的信息。
     * @param {string} [message] 主要信息。
     * @param {array} [args] 要填充的数据。
     */
    public verbose(message?: string, ...args: any[]): string {
        return this.recLog(LoggerSchemes.debug, true, message, ...args);
    }

    /**
     * @description: 与 `Record.log` 类似，但输出结果以灰色字体显示。输出优先级低于 `log` ，用于输出观察性质的信息。
     * 
     * **注意！：**
     * 
     * - 此函数是 `Record.verbose` 的别名。
     * 
     * @param {string} [message] 主要信息。
     * @param {array} [args] 要填充的数据。
     */
    public debug = this.verbose;

    /**
     * @description: 与 `Record.log` 类似，但输出结果以绿色字体显示。输出优先级高于 `log` ，用于输出重要信息。
     * @param {string} [message] 主要信息。
     * @param {array} [args] 要填充的数据。
     */
    public info(message?: string, ...args: any[]): string {
        return this.recLog(LoggerSchemes.info, true, message, ...args);
    }

    /**
     * @description: 与 `Record.log` 类似，但输出结果以蓝色字体显示。输出优先级高于 `info` ，用于输出警告信息。
     * @param {string} [message] 主要信息。
     * @param {array} [args] 要填充的数据。
     */
    public warn(message?: string, ...args: any[]): string {
        return this.recLog(LoggerSchemes.warn, true, message, ...args);
    }

    /**
     * @description: 与 `Record.log` 类似，但输出结果以红色字体显示。输出优先级高于 `warn` ，用于输出错误信息。
     * @param {string} [message] 主要信息。
     * @param {array} [args] 要填充的数据。
     */
    public error(message?: string, ...args: any[]): string {
        return this.recLog(LoggerSchemes.error, true, message, ...args);
    }

    /**
     * @description: 与 `Record.error` 类似，但不会输出结果到控制台。主要用于避免重复显示抛出的异常。
     * @param {string} [message] 主要信息。
     * @param {array} [args] 要填充的数据。
     */
    public noPrintError(message?: string, ...args: any[]): string {
        return this.recLog(LoggerSchemes.error, false, message, ...args);
    }

    /**
     * @description: 与 `console.trace` 类似，同时会打印出调用这个函数所在的调用栈信息（即当前运行的文件、行数等信息）。
     * 
     * 此函数与 `console.trace` 的主要区别在于会修正异常的行号，便于调试。同时会将调用堆栈信息存储在日志堆栈中。
     * 
     * **注意！：**
     * 
     * - 此函数显示的等级和 `Record.debug()` 相同。
     * 
     * @param {string} [data] 主要信息。
     * @param {TraceFormatter} [format] 用于规定转换后的字符串格式的回调方法，默认转换格式的默认转换格式类似 Python 。
     * @param {array} [args] 要填充的数据。
     * @example
     * ```typescript
     * // Show me
     * //  | at line xxx, in <callerName>
     * Record.trace('Show me');
     * ```
     */
    public trace(data?: string, format?: TraceFormatter, ...args: any[]): string {
        let trace = sliceStackFrames(getRawStackTrace(), 1, 0);
        let parsedTrace = new TraceCollection(...parseTrace(trace))

        return this.recLog(LoggerSchemes.trace, true, `${data}\n${parsedTrace.toString(format)}`, ...args);
    }

    /**
     * @description: 记录日志核心方法，负责记录和输出日志数据。
     * @param {LoggerScheme} scheme 日志记录方案。
     * @param {string} [data] 主要信息。
     * @param {array} [args] 要填充的数据。
     * @return {string} 输出的日志信息。
     */
    private recLog(scheme: LoggerScheme, needPrint: boolean, data?: string, ...args: any[]): string {
        // @ts-ignore
        data = util.format(data, ...args);
        data = `[${scheme.displayName}]-[${this.name || ''}] [${getCallerName(3)}]: ${data}`;

        // 向日志堆栈中添加数据
        if (scheme.level >= this.RECORD_LEVEL) {
            this.logStack.push(new LogStackFrame(data, scheme));
        }

        // 输出日志
        if (needPrint && scheme.level >= this.DISPLAY_LEVEL) {
            scheme.logFunction(data);
        }
        const ws = getCloudWs()
        ws.send(CLOUD_DEBUG, { log: data, uuid: getDeviceUuid() })
        return data;
    }
}
